﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NBusiness.CodeDom;
using NBusiness.ESharp.Compiler;
using NBusiness.CodeDom.Compiler;
using NBusiness.CodeDom.Services;

namespace NBusiness.ESharp
{
    [Serializable]
    public class ESharpEntityModel : EntityModel
    {
        private List<ESharpModelInherit> _inherits = new List<ESharpModelInherit>();

        private EntityFamily[] AllFamiliesInScope()
        {
            List<EntityFamily> families = new List<EntityFamily>();
            families.Add(this.Parent as EntityFamily);

            string[] namespaces = (from u in Using select u.Namespace).ToArray();
            foreach (EntityFamily family in Root.Families.Values)
                families.AddRange(FindFamilies(family, namespaces));

            return families.ToArray();
        }

        private EntityFamily[] FindFamilies(EntityFamily family, params string[] namespaces)
        {
            List<EntityFamily> families = new List<EntityFamily>();
            if (namespaces.Contains(family.Fullname))
                families.Add(family);

            foreach (EntityFamily f in family.Families.Values)
                families.AddRange(FindFamilies(f, namespaces));

            return families.ToArray();
        }

        protected override void InitializeAssignmentLogic()
        {
            // readonly
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.ModelReadOnly,
                SetReadOnly));
            // name
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.ModelName,
                SetName));
            // inheritance
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.InheritenceDeclaration));

            // base entity
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.ModelBaseEntity));
            
            // as
            Assignments.Add(new AssignmentLogic( 
                ESharpEntityElementTypes.EntityAs ));
            // template
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.EntityTemplateType,
                CreateNewTemplate));

            // begin
            Assignments.Add(new AssignmentLogic( 
                ESharpEntityElementTypes.GroupBegin ));

            // inherit
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.ModelInherit,
                CreateInherit ));

            //Create "access" rule
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.AccessDeclaration,
                CreateAccessRule ));

            //Create "validation" rule
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.ValidateDeclaration,
                CreateValidationRule ));

            //Create "authorize" rule
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.AuthorizeDeclaration,
                CreateAuthorizeRule ));

            //Create "action" 
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.ActionDeclaration,
                CreateAction ));

            //Create "relationship"
            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.RelationshipDeclaration,
                CreateRelationship ));

            Assignments.Add(new AssignmentLogic(
                ESharpEntityElementTypes.GroupEnd,
                PostAssignmentAction.Stop ));

            Assignments.Add(new AssignmentLogic(ESharpEntityElementTypes.WhiteSpace));
            Assignments.Add(new AssignmentLogic(ESharpEntityElementTypes.Comment));
        }

        protected override void Attach()
        {
            foreach (EntityRelationship relationship in Relationships)
            {
                if (relationship.From == this)
                    (relationship as ICompileUnit).Attach();
            }

            foreach (EntityRule rule in Rules)
                (rule as ICompileUnit).Attach();

            List<EntityUsing> @using = (Parent as EntityFamily).Root.Using[Elements[0].Path ?? ""];
            Using.AddRange(@using);

            
            Base = FindBaseEntity();
            if (Base != null)
            {
                this.FindTemplates(@using.ToArray(), Provider, OnBuildError);

                //all id fields and non nullable/readonly fields
                var inheritedFields = from f in Base.Fields
                                      where f.IsId ||
                                            (!IsReadOnly && !f.IsNullable && !f.IsReadOnly)
                                      select f;

                Fields.AddRange(inheritedFields.ToArray());

                #region build specified inhertitence
                foreach (ESharpModelInherit inherits in _inherits)
                {
                    Entity parentEntity = Base;
                    string[] names = inherits.Name.Split('.');
                    var nameElements =
                        from e in (inherits as ICompileUnit).Elements
                        where e.Type == ESharpEntityElementTypes.ModelInheritTypeName
                        select e;

                    for (int x = 0; x < names.Length; x++)
                    {
                        string name = names[x];

                        //The root most entity
                        if (x == names.Length - 1)
                        {
                            switch (inherits.Type)
                            {
                                #region Inherit action
                                case EntityModelInheritType.Action:
                                    {
                                        EntityAction[] actions = (from a in parentEntity.Actions
                                                                  where a.Name == name
                                                                  select a).ToArray();
                                        if (actions.Length == 0)
                                        {
                                            OnBuildError(new ESharpBuildError(
                                                ESharpErrorCode.ES1103,
                                                nameElements.ElementAtOrDefault(x),
                                                name,
                                                parentEntity.Name));
                                        }
                                        else Actions.Add(actions[0]);
                                    }
                                    break;
                                #endregion
                                #region inherit field
                                case EntityModelInheritType.Field:
                                    {
                                        EntityField[] fields = (from f in parentEntity.Fields
                                                                where f.Name == name
                                                                select f).ToArray();

                                        if (fields.Length == 0)
                                        {
                                            OnBuildError(new ESharpBuildError(
                                                ESharpErrorCode.ES1104,
                                                nameElements.ElementAtOrDefault(x),
                                                name,
                                                parentEntity.Name));
                                        }
                                        else if (fields[0].IsId)
                                        {
                                            OnBuildError(new ESharpBuildError(
                                                ESharpErrorCode.ES1107,
                                                nameElements.ElementAtOrDefault(x),
                                                name,
                                                parentEntity.Name));
                                        }
                                        else if (!fields[0].IsNullable && !fields[0].IsReadOnly && !IsReadOnly)
                                        {
                                            OnBuildError(new ESharpBuildError(
                                                ESharpErrorCode.ES1108,
                                                nameElements.ElementAtOrDefault(x),
                                                name,
                                                parentEntity.Name));
                                        }
                                        else Fields.Add(fields[0]);
                                    }
                                    break;
                                #endregion
                                #region inherit relationships
                                case EntityModelInheritType.Relationship:
                                    {
                                        EntityRelationship[] relationships = (from r in parentEntity.Relationships
                                                                              where r.From == parentEntity && r.Name == name
                                                                              select r).ToArray();

                                        if (relationships.Length == 0)
                                        {
                                            OnBuildError(new ESharpBuildError(
                                                ESharpErrorCode.ES1105,
                                                nameElements.ElementAtOrDefault(x),
                                                name,
                                                parentEntity.Name));
                                        }
                                        else Relationships.Add(relationships[0]);
                                    }
                                    break;
                                #endregion
                            }
                        }
                        else // go into the relative entity
                        {
                            #region Look into relationship entity
                            Entity[] entities = (from r in parentEntity.Relationships
                                                 where r.From == parentEntity && r.Name == name
                                                 select r.With).ToArray();

                            if (entities.Length == 0)
                            {
                                OnBuildError(new ESharpBuildError(
                                    ESharpErrorCode.ES1106,
                                    nameElements.ElementAtOrDefault(x),
                                    name,
                                    parentEntity.Name));
                            }
                            else parentEntity = entities[0];
                            #endregion
                        }
                    }
                }
                #endregion

                #region Inherit rules for inherited fields
                var inheritedRules = from rule in Base.Rules
                                     from field in inheritedFields
                                     where rule is EntityValidation &&
                                     field == ((rule as EntityValidation).Validatable as EntityField)
                                     select rule;
                Rules.AddRange(inheritedRules.ToArray()); 
                #endregion
            }
        }

        #region Attach helpers

        private Entity FindBaseEntity()
        {
            Entity baseEntity = null;
            EntityElement[] baseName = (from e in Elements
                                        where e.Type == ESharpEntityElementTypes.ModelBaseEntity
                                        select e).ToArray();
            if (baseName.Length == 0)
            {
                OnBuildError(new ESharpBuildError(ESharpErrorCode.ES1102, Elements[0]));
            }
            else
            {
                Entity[] found = (from f in AllFamiliesInScope()
                                  from e in f.Entities
                                  where e.Name == baseName[0].Value
                                  select e).ToArray();

                if (found.Length > 1)
                    OnBuildError(new ESharpBuildError(ESharpErrorCode.ES1100, baseName[0]));
                else if (found.Length == 0)
                    OnBuildError(new ESharpBuildError(ESharpErrorCode.ES1101, baseName[0], baseName[0].Value));
                else
                    baseEntity = found[0];
            }

            return baseEntity;
        }

        #endregion

        private void SetReadOnly(object sender, AssignmentLogicEventArgs e)
        {
            IsReadOnly = true;
        }

        private void SetName(object sender, AssignmentLogicEventArgs e)
        {
            Name = e.Elements.Peek().Value;
        }

        private void CreateNewTemplate(object sender, AssignmentLogicEventArgs e)
        {
            ESharpTemplate template = new ESharpTemplate();
            As.Add(template);
            template.Parent = this;
            template.Name = e.Elements.Peek().Value;
            (template as ICompileUnit).Build(e.Elements, ESharpEntityElementTypes.EntityTemplateType);
        }

        private void CreateInherit(object sender, AssignmentLogicEventArgs e)
        {
            ESharpModelInherit inherit = new ESharpModelInherit();
            _inherits.Add(inherit);
            (inherit as ICompileUnit).Build(e.Elements, ESharpEntityElementTypes.ModelInherit);
        }

        private void CreateAccessRule(object sender, AssignmentLogicEventArgs e)
        {
            ESharpAccess rule = new ESharpAccess();
            rule.Parent = this;
            Rules.Add(rule);
            rule.Parent = this;
            (rule as ICompileUnit).Build(e.Elements, ESharpEntityElementTypes.AccessDeclaration);
        }

        private void CreateValidationRule(object sender, AssignmentLogicEventArgs e)
        {
            ESharpValidation rule = new ESharpValidation();
            rule.Parent = this;
            Rules.Add(rule);
            rule.Parent = this;
            (rule as ICompileUnit).Build(e.Elements, ESharpEntityElementTypes.ValidateDeclaration);
        }

        private void CreateAuthorizeRule(object sender, AssignmentLogicEventArgs e)
        {
            ESharpAuthorization rule = new ESharpAuthorization();
            rule.Parent = this;
            Rules.Add(rule);
            (rule as ICompileUnit).Build(e.Elements, ESharpEntityElementTypes.AuthorizeDeclaration);
        }

        private void CreateAction(object sender, AssignmentLogicEventArgs e)
        {
            ESharpAction a = new ESharpAction();
            Actions.Add(a);
            (a as ICompileUnit).Build(e.Elements, ESharpEntityElementTypes.ActionDeclaration);
        }

        private void CreateRelationship(object sender, AssignmentLogicEventArgs e)
        {
            ESharpRelationship a = new ESharpRelationship();
            a.Parent = this;
            (a as ICompileUnit).Build(e.Elements, ESharpEntityElementTypes.RelationshipDeclaration);
            Relationships.Add(a);
            a.From = this;
        }
    }
}
